from __future__ import annotations

from datetime import date, timedelta
from typing import List, Optional

from django.db import transaction
from django.db.models import Avg
from django.utils import timezone

from .models import KnowledgeNode, LearningPath, PathNode, ReviewTask, StudyRecord


class LearningProgressService:
    """
    负责在新增学习记录后同步知识节点、路径节点以及整体路径的进度。
    """

    def __init__(self, user):
        self.user = user

    def handle_study_record(self, record: StudyRecord):
        with transaction.atomic():
            self._update_node_mastery(record.node, record.duration_minutes)
            affected_paths = self._sync_path_nodes(record.node)

        scheduler = ReviewScheduler(self.user)
        scheduler.schedule_for_node(record.node, reference_date=record.created_at.date())

        return affected_paths

    def _update_node_mastery(self, node: KnowledgeNode, duration_minutes: int):
        gain = self._calculate_mastery_gain(duration_minutes)
        node.mastery_level = min(100, node.mastery_level + gain)
        node.save(update_fields=['mastery_level', 'updated_at'])

    @staticmethod
    def _calculate_mastery_gain(duration_minutes: int) -> int:
        if duration_minutes <= 0:
            return 2
        # 以 15 分钟为一个档位，最多提升 20
        gain = int(duration_minutes / 15 * 8)
        gain = max(3, min(20, gain))
        return gain

    def _sync_path_nodes(self, node: KnowledgeNode):
        path_nodes = PathNode.objects.select_related('path').filter(
            node=node,
            path__user=self.user
        )
        updated_path_ids = set()

        for path_node in path_nodes:
            updated_path_ids.add(path_node.path_id)
            previous_status = path_node.status
            path_node.progress = node.mastery_level

            if node.mastery_level >= 80:
                path_node.status = 'completed'
                if not path_node.completed_at:
                    path_node.completed_at = timezone.now()
            elif node.mastery_level > 0 and path_node.status == 'locked':
                path_node.status = 'in_progress'
                if not path_node.started_at:
                    path_node.started_at = timezone.now()

            path_node.save(update_fields=['progress', 'status', 'started_at', 'completed_at'])

            if previous_status != 'completed' and path_node.status == 'completed':
                self._unlock_next_node(path_node.path, path_node.order)

        for path_id in updated_path_ids:
            self._update_path_progress(path_id)

        return updated_path_ids

    def _unlock_next_node(self, path: LearningPath, completed_order: int):
        next_nodes = path.pathnode_set.filter(order__gt=completed_order, status='locked').order_by('order')
        for path_node in next_nodes:
            if self._prerequisites_met(path_node.node):
                path_node.status = 'available'
                path_node.started_at = path_node.started_at or timezone.now()
                path_node.save(update_fields=['status', 'started_at'])
                break

    def _update_path_progress(self, path_id: int):
        path = LearningPath.objects.get(id=path_id, user=self.user)
        agg = path.pathnode_set.aggregate(avg_progress=Avg('progress'))
        avg_progress = int(agg['avg_progress'] or 0)
        path.progress = avg_progress

        if path.progress >= 100 and path.status != 'completed':
            path.status = 'completed'
            path.completed_at = path.completed_at or timezone.now()
        elif path.progress > 0 and path.status == 'not_started':
            path.status = 'in_progress'
            path.started_at = path.started_at or timezone.now()

        path.save(update_fields=['progress', 'status', 'started_at', 'completed_at', 'updated_at'])

    def _prerequisites_met(self, node: KnowledgeNode) -> bool:
        prerequisites = node.prerequisites.filter(user=self.user)
        if not prerequisites.exists():
            return True
        return not prerequisites.filter(mastery_level__lt=60).exists()


class ReviewScheduler:
    """
    根据节点掌握程度使用简化的间隔重复算法生成复习任务。
    """

    def __init__(self, user):
        self.user = user

    def schedule_for_node(self, node: KnowledgeNode, reference_date: Optional[date] = None):
        reference_date = reference_date or date.today()

        # 完成或提前学习后，将今天之前的 pending 任务标记为完成
        ReviewTask.objects.filter(
            user=self.user,
            node=node,
            status='pending',
            scheduled_date__lte=reference_date
        ).update(status='completed', completed_at=timezone.now())

        intervals = self._pick_intervals(node.mastery_level)
        for interval in intervals:
            scheduled_date = reference_date + timedelta(days=interval)
            ReviewTask.objects.update_or_create(
                user=self.user,
                node=node,
                scheduled_date=scheduled_date,
                defaults={
                    'status': 'pending',
                }
            )

    @staticmethod
    def _pick_intervals(mastery_level: int) -> List[int]:
        if mastery_level < 30:
            return [1, 3, 5]
        if mastery_level < 60:
            return [3, 7, 14]
        if mastery_level < 85:
            return [7, 14, 30]
        return [14, 30, 60]
